﻿#region Usings

using System;
using System.Collections.Specialized;
using System.Globalization;
using System.IO;
using System.Net;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Web;
using System.Xml;
using System.Xml.XPath;
using Kazkommertsbank.Framework.Common;
using Kazkommertsbank.Framework.Model;
using Kazkommertsbank.Framework.Model.OrderStatus;
using Kazkommertsbank.Framework.Model.Pay;

#endregion

namespace Kazkommertsbank.Framework
{
    public class KkbController
    {
        private const string CURRENCY = "398";
        private readonly IKkbSettings _settings;

        public KkbController(IKkbSettings settings)
        {
            _settings = settings;
        }

        public void StartPaymentTransaction(IKkbOrder order)
        {
            RemoteForm form = new RemoteForm("kazkommertsbankForm", _settings.IsTestMode ? _settings.TestModePostUrl : _settings.PostUrl);
            NameValueCollection fields = new NameValueCollection();

            fields.Add("Signed_Order_B64", BuildSignedOrder(order.OrderID, order.Amount));

            if(!string.IsNullOrEmpty(_settings.ShopID))
                fields.Add("ShopID", _settings.ShopID);

            fields.Add("email", order.Email);
            fields.Add("BackLink", UrlHelper.ResolveUrl(_settings.BackLink).ToString());

            if(!string.IsNullOrEmpty(_settings.FailureBackLink))
                fields.Add("FailureBackLink", UrlHelper.ResolveUrl(_settings.FailureBackLink).ToString());

            fields.Add("PostLink", UrlHelper.ResolveUrl(_settings.PostCallbackLink).ToString());

            if(!string.IsNullOrEmpty(_settings.FailurePostCallbackLink))
                fields.Add("FailurePostLink", UrlHelper.ResolveUrl(_settings.FailurePostCallbackLink).ToString());

            if(!string.IsNullOrEmpty(_settings.Language))
                fields.Add("Language", _settings.Language);

            fields.Add("appendix", Convert.ToBase64String(Utils.ConvertStringToByteArray(order.Appendix.ToXmlString()), Base64FormattingOptions.None));
            form.Fields.Add(fields);

            form.Post();
        }

        public string CreateOrderXml(int orderID, decimal amount)
        {
            string certFile = HttpContext.Current.Server.MapPath(_settings.PathToCertificate);
            if(string.IsNullOrEmpty(certFile) || !File.Exists(certFile))
                return "";

            PaymentOrder order = new PaymentOrder();
            order.Amount = amount.ToString("0.00", CultureInfo.InvariantCulture);
            order.Currency = CURRENCY;
            order.DepartmentAmount = amount.ToString("0.00", CultureInfo.InvariantCulture);
            order.DepartmentMerchantId = _settings.MerchantID;
            order.OrderId = orderID.ToString("000000", CultureInfo.InvariantCulture);

            PaymentMerchant merchant = new PaymentMerchant();
            merchant.CertId = _settings.CertificateID;
            merchant.Name = _settings.ShopID;
            merchant.Order = order;

            string strForSign = merchant.ToXmlString();

            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = new ASCIIEncoding();
            settings.Indent = false;
            settings.OmitXmlDeclaration = true;

            StringBuilder result = new StringBuilder();
            using(XmlWriter writer = XmlWriter.Create(result, settings))
            {
                writer.WriteStartElement("document");
                writer.WriteRaw(strForSign);
                writer.WriteStartElement("merchant_sign");
                writer.WriteAttributeString("type", "RSA");
                writer.WriteString(Sign64(strForSign));
                writer.WriteEndElement();
                writer.WriteEndElement();
            }

            return result.ToString();
        }

        public string BuildSignedOrder(int orderID, decimal amount)
        {
            return Convert.ToBase64String(Utils.ConvertStringToByteArray(CreateOrderXml(orderID, amount)), Base64FormattingOptions.None);
        }

        public ResponseDocumentBase GetOrderStatus(int orderID)
        {
            string response;
            using(WebClient webClient = new WebClient())
            {
                try
                {
                    response = webClient.UploadString(_settings.OrderStatusCheckUrl, "GET", CreateStatusRequestXml(orderID));
                }
                catch(Exception)
                {
                    return null;
                }
            }

            if(!string.IsNullOrEmpty(response))
            {
                try
                {
                    ResponseDocumentBase kazkommertsbankResponse;
                    XPathDocument doc = new XPathDocument(new StringReader(response));
                    XPathNavigator nav = doc.CreateNavigator();
                    if(nav.SelectSingleNode("//error") != null)
                    {
                        kazkommertsbankResponse = new OrderStatusErrorDocument(response);
                        if(kazkommertsbankResponse.SuccessfulParsed)
                            return kazkommertsbankResponse;
                    }
                    else if(nav.SelectSingleNode("//document/bank") != null)
                    {
                        kazkommertsbankResponse = new OrderStatusSuccessDocument(response);
                        if(kazkommertsbankResponse.SuccessfulParsed)
                            return kazkommertsbankResponse;
                    }
                    else
                        return null;
                }
                catch
                {
                    return null;
                }
            }

            return null;
        }

        public string CreateStatusRequestXml(int orderId)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Encoding = new ASCIIEncoding();
            settings.Indent = false;
            settings.OmitXmlDeclaration = true;

            StringBuilder merchant = new StringBuilder();
            using(XmlWriter writer = XmlWriter.Create(merchant, settings))
            {
                writer.WriteStartElement("merchant");
                writer.WriteAttributeString("id", _settings.MerchantID);
                writer.WriteStartElement("order");
                writer.WriteAttributeString("id", orderId.ToString("000000", CultureInfo.InvariantCulture));
                writer.WriteEndElement();
                writer.WriteEndElement();
            }

            StringBuilder result = new StringBuilder();
            using(XmlWriter writer = XmlWriter.Create(result, settings))
            {
                writer.WriteStartElement("document");
                writer.WriteRaw(merchant.ToString());
                writer.WriteStartElement("merchant_sign");
                writer.WriteAttributeString("type", "RSA");
                writer.WriteAttributeString("cert_id", _settings.CertificateID);
                writer.WriteString(Sign64(merchant.ToString()));
                writer.WriteEndElement();
                writer.WriteEndElement();
            }

            return result.ToString();
        }

        public bool Verify(string strForVerify, string sign)
        {
            string publicKeyFile = HttpContext.Current.Server.MapPath(_settings.PathToKazkommertsbankPublicKey);
            if(string.IsNullOrEmpty(publicKeyFile) || !File.Exists(publicKeyFile))
                return false;

            X509Certificate2 kkbCert = new X509Certificate2(_settings.PathToKazkommertsbankPublicKey);
            RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)kkbCert.PublicKey.Key;
            byte[] bytesForVerify = Utils.ConvertStringToByteArray(strForVerify);
            byte[] bytesSign = Convert.FromBase64String(sign);
            Array.Reverse(bytesSign);
            bool result;
            try
            {
                result = rsa.VerifyData(bytesForVerify, "SHA1", bytesSign);
            }
            catch
            {
                result = false;
            }
            return result;
        }

        public string Sign64(string strForSign)
        {
            X509Certificate2 kkbCert = new X509Certificate2(HttpContext.Current.Server.MapPath(_settings.PathToCertificate), _settings.CertificatePassword);
            RSACryptoServiceProvider rsa = (RSACryptoServiceProvider)kkbCert.PrivateKey;
            byte[] signData = rsa.SignData(Utils.ConvertStringToByteArray(strForSign), "SHA1");
            Array.Reverse(signData);
            return Convert.ToBase64String(signData, Base64FormattingOptions.None);
        }
    }
}